<?php
/**
 * Created by PhpStorm.
 * User: Inhere
 * Date: 2015/3/4
 * Time: 19:44
 * Use :
 * File: DatabaseModel.php
 */

namespace ulue\core\model;

use ulue\core\db\base\AbstractDbDriver;
use ulue\core\db\base\command\AbstractTable;
use ulue\core\db\base\schema\Schema;
use ulue\core\db\base\query\AbstractDataQuery;

abstract class DatabaseModel extends AbstractModel
{

    // 数据模型集合
    static private $_models  = [];

    /**
     * Property query.
     * @var  AbstractDataQuery
     */
    static private $_query  = null;

    protected $_related  = [];

    // 是新的记录，一个空模型
    private $_isNew = true;

    /**
     * 旧的
     * 一个数据模型(即一条数据库记录)的各个字段及对应值的数据信息
     * @var array
     */
    protected $_oldAttrs = [];

    /**
     * 一个数据模型(即一条数据库记录)的各个字段及对应值的数据信息
     * @var array
     */
    protected $_attrs = [];

    // 查询到的数据集合
    protected $_dataSet = [];       //

    /**
     * CURD operate
     */
    const OP_CREATE = 'create';
    const OP_UPDATE = 'update';
    const OP_READ   = 'read'; // serach
    const OP_DELETE = 'delete';

    const  ONE_TO_ONE   = 'OneToOne';
    const  ONE_TO_MANY  = 'OneToMany';
    const  MANY_TO_ONE  = 'ManyToOne';
    const  MANY_TO_MANY = 'ManyToMany';


    /**
     * @param string $scenario
     */
    public function __construct($scenario='insert')
    {
        if ($scenario===null) {
            return;
        }

        $this->setScenario($scenario);

        $this->init();
    }

    public function init()
    {
        $db = static::getDb();

        // 请求验证表
        $db->verifyTable( static::realTableName() );

        $columns       = static::getSchema()->columns;

        foreach ($columns as $name => $column) {
            $this->_attrs[$name] = $column->default;
        }
    }

    /**
     * 获取数据模型，请在子类中继承使用
     * @param  string $classname
     * @return static
     */
    static public function model($classname=null)
    {
        if ($classname===null) {
            $classname = static::classname();
        }

        if ( !isset(self::$_models[$classname]) ) {
            self::$_models[$classname] = new $classname();
        }

        return self::$_models[$classname];
    }

////////////////////////////////////////// Attribute ///////////////////////////////////////

    /**
     * @param array $attrs
     * @return $this
     */
    public function setOldAttrs(array $attrs)
    {
        $this->_oldAttrs = $attrs;

        return $this;
    }

    /**
     * @return array
     */
    public function getOldAttrs()
    {
        return $this->_oldAttrs;
    }

    /**
     * @param $name
     * @param $value
     * @return $this
     */
    public function setOldAttr($name, $value)
    {
        if (isset($this->_oldAttrs[$name])) {
            $this->_oldAttrs[$name] = $value;
        }

        return $this;
    }

    /**
     * @param $name
     * @return null
     */
    public function getOldAttr($name)
    {
        return isset($this->_oldAttrs[$name]) ? $this->_oldAttrs[$name]: null;
    }

    /**
     * 属性值是否发生改变
     * @param $name
     * @return bool
     */
    public function attrIsChanged($name)
    {
        if (isset($this->_oldAttrs[$name], $this->_attrs[$name])) {
            return $this->_oldAttrs[$name] !== $this->_attrs[$name];
        }

        return isset($this->_oldAttrs[$name]) || isset($this->_attrs[$name]);
    }

    abstract public function setAttr($name, $value);

    abstract public function getAttr($name);

////////////////////////////////////////// database table ///////////////////////////////////////

    /**
     * 切换当前的数据库连接,得到数据库对象
     * @access public
     * @throws \ulue\core\db\TheDatabaseException
     * @return AbstractDbDriver
     */
    static public function getDb()
    {
        /**
         * @var $db AbstractDbDriver
         */
        $db = \Ioc::get('db');

        return $db;
    }

    /**
     * getQuery
     * @param bool $new
     * @throws \DMissArgumentException
     * @return  AbstractDataQuery
     */
    static public function getQuery($new=false)
    {
        $query = static::getDb()->getQuery($new);

        if ($query != self::$_query) {
            $query->setTable( static::realTableName() );
        }

        self::$_query = $query;

        return self::$_query;
    }

    /**
     * 当有设置类似 [[tableName]] ， 即有 [[]] 引起来 表名时，表示表名不是完整的会使用设置的前缀来组成真实的表名
     * 仅有 'tableName' 时，表示设置的已是个完整表名称，不会再给它添加前缀
     * @return string
     */
    static public function tableName()
    {
        // return lcfirst( basename(get_called_class()) );
        return '[['.lcfirst( basename(get_called_class()) ).']]';
    }

    /**
     * get real TableName e.g. prefix_Table
     */
    static public function realTableName()
    {
        return  static::getDb()->resolveTableName(static::tableName());
    }

    /**
     * get query TableName e.g. myDB.myTable
     */
    static public function queryTableName()
    {
        return static::getDb()->queryTableName(static::realTableName() );
    }

    /**
     * @return AbstractTable
     */
    static public function getTable()
    {
        return static::getDb()->getTable( static::realTableName()  );
    }

    /**
     * @return array
     */
    static public function getTableDetail()
    {
        return static::getDb()->getDatabase()->getTableDetail(static::realTableName() );
    }

    /**
     * @return Schema
     */
    static public function getSchema()
    {
        return static::getDb()->getTableSchema(static::realTableName() );
    }

    /**
     * @return Schema
     */
    static public function getTableSchema()
    {
        return static::getSchema();
    }

////////////////////////////////////////// Column ///////////////////////////////////////

    /**
     * @return array|\ulue\core\db\base\schema\Column[]
     */
    public function getColumns()
    {
        return $this->getSchema()->columns;
    }

    /**
     * 当前模型记录的主键值，可能有多个
     * @param bool $toString
     * @return mixed
     */
    abstract public function getPriKey($toString=true);

    /**
     * 当前模型记录的之前的主键值，可能有多个
     * @return mixed
     */
    abstract public function getOldPriKey();

    /**
     * @param $value
     * @return mixed
     */
    abstract public function setOldPriKey($value);

    /**
     * 得到当前表的主键列名
     * @return \string[]
     */
    static public function priKeyName()
    {
        return static::getSchema()->priKey;
    }

    /**
     * 得到当前表的唯一主键列名，复合主键时，则是获取前导列列名
     * @return \string[]
     */
    public function getUniquePk()
    {
        return static::priKeyName()[0];
    }

    /**
     * @return array
     */
    static public function foreKeyName()
    {
        return static::getSchema()->foreKey;
    }

////////////////////////////////////////// data ///////////////////////////////////////


    /**
     * @return void
     */
    abstract public function afterFind();

    /**
     * @return bool
     */
    abstract public function beforeFind();

    /**
     * 填充一个模型(一条记录)的属性数据
     * @param  array  $attrs
     * @param  boolean $callAfterFind
     * @return object AbstractModel
     */

    /**
     * 填充模型列表(查询的多条记录)的属性数据
     * @param array $data
     * @param  boolean $callAfterFind
     * @param  string $key 设置某一列名为模型列表的键
     * @return array
     */

    /**
     * @return self
     */
    static public function getNewInstance()
    {
        return new static;
    }

    /**
     * @param $value
     * @return $this
     */
    public function setIsNew($value)
    {
        $this->_isNew = (bool)$value;

        return $this;
    }

    /**
     * @return bool
     */
    public function getIsNew()
    {
        return $this->_isNew;
    }

////////////////////////////////////////// loading data //////////////////////////////////////////

    /**
     * 填充一个模型(一条记录)的属性数据
     * @param  array  $attrs
     * @return object AbstractModel
     */
    public function paddingModel(array $attrs)
    {
        if (!$attrs || !is_array($attrs)) {
            return null;
        }

        /**
         * @var $model self
         */
        // $model  = static::getNewInstance();
        $this->setScenario('update')->setIsNew(false);
        $columns = array_flip($this->attrNames());

        foreach ($attrs as $name => $value) {
            if(isset($columns[$name]))
            {
                $this->_attrs[$name]=$value;
            } else
            {
                $this->$name=$value;
            }
        }

        $this->setOldPriKey( $this->getPriKey() );
        $this->setOldAttrs( $this->getAttrs() );
//de($this,$attrs);

        // return $this;
    }

    /**
     * 填充模型列表(查询的多条记录)的属性数据
     * @param  array  $attrs
     * @param  boolean $callAfterFind
     * @param  string $key 设置某一列名为模型列表的键
     * @return array
     */
    public function paddingModels(array $data, $callAfterFind=true, $key=null)
    {
        $models=[];

        foreach($data as $attrs) {
            if( $model=$this->paddingModel($attrs,$callAfterFind) ) {
                if(!$key) {
                    $models[] = $model;
                } else {
                    $models[$model->$key]=$model;
                }
            }
        }

        $models = new \ArrayObject($models);

        return $models;
    }

////////////////////////////////////////// table alias /////////////////////////////////////////////

    /**
     * setTableAlias 设置真实表名对应的别名
     * @param string $alias [别名]
     * @param string $realName [表名]
     * @return $this
     */
    public function setTableAlias($alias, $realName)
    {
        if ( $alias && $realName && is_string($realName) && is_string($alias)) {
            self::$tableAliases[$alias] = $realName;
        }

        return $this;
    }

    /**
     * getTableAlias 通过真实表名得到其对应的别名
     * @param  string $realName
     * @return string
     */
    public function getTableAlias($realName)
    {
        $tableAliases = self::$tableAliases;
        $tableAliases = array_flip($tableAliases);

        return isset($tableAliases[$realName]) ? $tableAliases[$realName] :null;
    }

    /**
     * getTableByAlias 通过别名得到其对应的真实表名
     * @param  string $alias
     * @return string
     */
    public function getTableByAlias($alias)
    {
        return isset(self::$tableAliases[$alias]) ? self::$tableAliases[$alias] :$alias;
    }


}// end class DatabaseModel